package ar.uba.fi.RSACryptoFiuba.model.decrypt;

import java.math.BigInteger;

import org.apache.commons.lang3.StringUtils;

import ar.uba.fi.RSACryptoFiuba.model.InvalidValueException;
import ar.uba.fi.RSACryptoFiuba.model.RSACalculator;
import ar.uba.fi.RSACryptoFiuba.model.RSAMessage;
import ar.uba.fi.RSACryptoFiuba.model.RSAStep;

public class DecryptStep extends RSAStep {
	
	@Override
	public void execute(RSAMessage message) {
		RSADecryptMessage decryptMessage = (RSADecryptMessage) message;
		String mWithLeadingZeros = getMWithLeadingZeros(decryptMessage);
		updateDecryptMessage(decryptMessage, mWithLeadingZeros);
		
		decryptMessage.update();
	}
	
	@Override
	public void undo(RSAMessage message) {
		RSADecryptMessage decryptMessage = (RSADecryptMessage) message;
		
		int nKeylength = decryptMessage.getNKey().length();		
		String numericalRepresentation = decryptMessage.getNumericalRepresentation();	 
		String remainEncryptedText = decryptMessage.getRemainEncryptedText();
		String processedEncryptedText = decryptMessage.getProcessedEncryptedText();
		String actualC = decryptMessage.getActualC();
		
		String newProcessedEncryptedText = processedEncryptedText.substring(0, processedEncryptedText.length() - nKeylength);
		decryptMessage.setProcessedEncryptedText(newProcessedEncryptedText);
		
		String newActualC = (newProcessedEncryptedText.length()>0) ? processedEncryptedText.substring(newProcessedEncryptedText.length() - nKeylength, newProcessedEncryptedText.length()) : "";
		decryptMessage.setActualC(newActualC);
		
		String newRemainEncryptedText = actualC + remainEncryptedText;
		decryptMessage.setRemainEncryptedText(newRemainEncryptedText);
		
		String newNumericalRepresentation = numericalRepresentation.substring(0, numericalRepresentation.length() - (nKeylength -1));
		decryptMessage.setNumericalRepresentation(newNumericalRepresentation);
		
		String newActualM = (newNumericalRepresentation.length() >= (nKeylength -1)) ? newNumericalRepresentation.substring(newNumericalRepresentation.length() - (nKeylength -1), newNumericalRepresentation.length()) : "";
		decryptMessage.setActualM(newActualM);
			
		decryptMessage.update();
	}

	@Override
	public void test(RSAMessage message) {
		RSADecryptMessage decryptMessage = (RSADecryptMessage) message;
		String element = decryptMessage.getElementToEvaluate();
		String mWithLeadingZeros = getMWithLeadingZeros(decryptMessage);
		boolean isValid = mWithLeadingZeros.equals(element);
		if(!isValid){
			throw new InvalidValueException("El valor m debe ser igual a c^d mod n.");
		}
		updateDecryptMessage(decryptMessage, element);
		
		decryptMessage.update();
	}
	
	private String getMWithLeadingZeros(RSADecryptMessage decryptMessage) {
		int l = decryptMessage.getNKey().length();
		String remainEncryptedText = decryptMessage.getRemainEncryptedText();
		String c = remainEncryptedText.substring(0, l);
		BigInteger m = RSACalculator.exp(new BigInteger(c), Integer.valueOf(decryptMessage.getDKey()));
		m = RSACalculator.mod(m, new BigInteger(decryptMessage.getNKey()));
		return completeWithLeadingZeros(m, l-1);
	}

	private String completeWithLeadingZeros(BigInteger number, int length) {
		return StringUtils.leftPad(number.toString(), length, "0");
	}

	private void updateDecryptMessage(RSADecryptMessage decryptMessage, String mWithLeadingZeros) {
		String remainEncryptedText = decryptMessage.getRemainEncryptedText();
		int l = decryptMessage.getNKey().length();
		decryptMessage.setRemainEncryptedText(remainEncryptedText.substring(l, remainEncryptedText.length()));
		
		String processedEncryptedText = decryptMessage.getProcessedEncryptedText();
		String newProcessedEncryptedText = (remainEncryptedText.length() > 0) ? processedEncryptedText + remainEncryptedText.substring(0,l) : processedEncryptedText;
		decryptMessage.setProcessedEncryptedText(newProcessedEncryptedText);
		
		decryptMessage.setActualC(remainEncryptedText.substring(0,l));
		
		decryptMessage.setActualM(mWithLeadingZeros);
		decryptMessage.setNumericalRepresentation(decryptMessage.getNumericalRepresentation() + mWithLeadingZeros);		
	}

}
